package process.service.impl;

import gate.Annotation;
import gate.AnnotationSet;
import gate.Corpus;
import gate.Factory;
import gate.FeatureMap;
import gate.Gate;
import gate.ProcessingResource;
import gate.creole.ANNIEConstants;
import gate.creole.SerialAnalyserController;
import gate.util.GateException;
import gate.util.OffsetComparator;
import gate.util.persistence.PersistenceManager;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;

import javax.annotation.PostConstruct;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import process.model.AnnotatedPage;
import process.model.event.AnnotatedPageEvent;
import process.model.event.MenuEvent;
import process.model.lookup.ProductLookup;
import process.service.AnnieProcessService;

import com.google.common.collect.Lists;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;

import core.constant.SystemConstant;

@Service
public class AnnieProcessServiceImpl implements AnnieProcessService {

	@Autowired
	private EventBus eventBus;

	private SerialAnalyserController controller;
	private Corpus corpus;

	@PostConstruct
	public void initialize() throws IOException, GateException {
		eventBus.register(this);

		FeatureMap params;
		ProcessingResource pr;

		// Init path to ANNIE Plugin
		Properties props = System.getProperties();
		props.setProperty("gate.home", SystemConstant.GATE_HOME);

		// initialise the GATE library
		Gate.init();

		// load ANNIE as an application from a gapp file
		PersistenceManager.loadObjectFromFile(new File(new File(Gate
				.getPluginsHome(), ANNIEConstants.PLUGIN_DIR),
				ANNIEConstants.DEFAULT_FILE));

		// create a serial analyser controller to run ANNIE with
		controller = (SerialAnalyserController) Factory.createResource(
				"gate.creole.SerialAnalyserController",
				Factory.newFeatureMap(), Factory.newFeatureMap(), "ANNIE_"
						+ Gate.genSym());

		// Add process DOCUMENT ANNOTATION RESET
		params = Factory.newFeatureMap();
		pr = (ProcessingResource) Factory.createResource(
				"gate.creole.annotdelete.AnnotationDeletePR", params);
		controller.add(pr);

		// Add process TOKENISER
		params = Factory.newFeatureMap();
		pr = (ProcessingResource) Factory.createResource(
				"gate.creole.tokeniser.DefaultTokeniser", params);
		controller.add(pr);

		// Add process GAZETTEER
		params = Factory.newFeatureMap();
		params.put("caseSensitive", new Boolean(false));
		pr = (ProcessingResource) Factory.createResource(
				"gate.creole.gazetteer.DefaultGazetteer", params);
		controller.add(pr);

		// Add process NE TRANSDUCER
		params = Factory.newFeatureMap();
		pr = (ProcessingResource) Factory.createResource(
				"gate.creole.ANNIETransducer", params);
		controller.add(pr);

		// Initail a Corpus
		corpus = (Corpus) Factory.createResource("gate.corpora.CorpusImpl");
		controller.setCorpus(corpus);

	}

	@Override
	public AnnotatedPage annotate(String link) throws Exception {
		corpus.clear();

		// Get page and clean Html tags
		Document docJsoup = Jsoup.connect(link)
				.timeout(SystemConstant.CONNECT_TIMEOUT).get();
		// String text = Jsoup.parse(docJsoup.toString()).text();

		// Add new Document into Corpus and Excute
		FeatureMap params = Factory.newFeatureMap();
		// params.put(gate.Document.DOCUMENT_STRING_CONTENT_PARAMETER_NAME,
		// text);
		params.put("sourceUrl", link);
		params.put("encoding", "UTF-8");
		params.put("markupAware", new Boolean(true));
		params.put("preserveOriginalContent", new Boolean(true));
		params.put("collectRepositioningInfo", new Boolean(true));
		gate.Document docGate = (gate.Document) Factory.createResource(
				"gate.corpora.DocumentImpl", params);
		corpus.add(docGate);
		controller.execute();

		// Get Annotated documents
		AnnotationSet defaultAnnotSet = docGate.getAnnotations();
		Set<String> annotTypesRequired = new HashSet<String>();
		annotTypesRequired.add("Product");
		annotTypesRequired.add("Price");

		// Get Annotation set and convert into List
		List<Annotation> annotationList = new ArrayList<Annotation>(
				new HashSet<Annotation>(defaultAnnotSet.get(annotTypesRequired)));
		// Sort by offset
		Collections.sort(annotationList, new OffsetComparator());

		// detect Product
		return new AnnotatedPage(docJsoup, detectProduct(annotationList));
	}

	@Override
	public AnnotatedPage annotateDoc(Document doc) throws Exception {
		FeatureMap params = Factory.newFeatureMap();
		params.put(gate.Document.DOCUMENT_STRING_CONTENT_PARAMETER_NAME,
				doc.toString());
		params.put("encoding", "UTF-8");
		params.put("markupAware", new Boolean(true));
		params.put("preserveOriginalContent", new Boolean(true));
		params.put("collectRepositioningInfo", new Boolean(true));
		gate.Document docGate = (gate.Document) Factory.createResource(
				"gate.corpora.DocumentImpl", params);
		corpus.add(docGate);
		controller.execute();

		// Get Annotated documents
		AnnotationSet defaultAnnotSet = docGate.getAnnotations();
		Set<String> annotTypesRequired = new HashSet<String>();
		annotTypesRequired.add("Product");
		annotTypesRequired.add("Price");

		// Get Annotation set and convert into List
		List<Annotation> annotationList = new ArrayList<Annotation>(
				new HashSet<Annotation>(defaultAnnotSet.get(annotTypesRequired)));
		// Sort by offset
		Collections.sort(annotationList, new OffsetComparator());

		// detect Product
		return new AnnotatedPage(doc, detectProduct(annotationList));
	}

	public String getProduct(String text) throws Exception {
		corpus.clear();
		String product = "";
		FeatureMap params = Factory.newFeatureMap();
		params.put("encoding", "UTF-8");
		params.put(gate.Document.DOCUMENT_STRING_CONTENT_PARAMETER_NAME, text);
		gate.Document docGate = (gate.Document) Factory.createResource(
				"gate.corpora.DocumentImpl", params);
		corpus.add(docGate);
		controller.execute();
		AnnotationSet defaultAnnotSet = docGate.getAnnotations();
		Set<String> annotTypesRequired = new HashSet<String>();
		annotTypesRequired.add("Company");
		List<Annotation> annotationList = new ArrayList<Annotation>(
				new HashSet<Annotation>(defaultAnnotSet.get(annotTypesRequired)));
		product = getText(annotationList.get(0));
		return product;
	}

	public List<ProductLookup> detectProduct(List<Annotation> annotations) {
		List<ProductLookup> list = new ArrayList<ProductLookup>();
		ProductLookup product = new ProductLookup();
		for (int i = 0; i < annotations.size(); i++) {
			if (annotations.get(i).getType() == "Product") {
				try {

					// 1st case: Product + Price + Price
					if (annotations.get(i + 1).getType() == "Price"
							&& annotations.get(i + 2).getType() == "Price") {
						long first = annotations.get(i + 1).getEndNode()
								.getOffset();
						long second = annotations.get(i + 2).getStartNode()
								.getOffset();
						// check if the adjacent prices are too far
						if ((second - first) < SystemConstant.PRICE_DISTANCE) {
							long price1 = getNumber(getText(annotations
									.get(i + 1)));
							long price2 = getNumber(getText(annotations
									.get(i + 2)));
							product = new ProductLookup(
									getText(annotations.get(i)), min(price1,
											price2));
							list.add(product);
							// Move forward 2 index
							i += 2;
							continue;
						}
					}

					// 2nd case: Product + Price
					if (annotations.get(i + 1).getType() == "Price") {
						long price = getNumber(getText(annotations.get(i + 1)));
						product = new ProductLookup(
								getText(annotations.get(i)), price);
						list.add(product);
						// Move forward 1 index
						i++;
						continue;
					}
				} catch (IndexOutOfBoundsException ex) {

				} catch (Exception ex) {
					ex.printStackTrace();
				}
			}
		}
		return list;
	}

	private String getText(Annotation annotation) {
		return Jsoup.parse(annotation.getFeatures().get("text").toString())
				.text();
	}

	private long getNumber(String text) throws Exception {
		long num = Long.parseLong(text.replaceAll("[^\\d]", ""));
		return num;
	}

	private long min(long x, long y) {
		return (x < y) ? x : y;
	}

	@Subscribe
	public void handleMenuEvent(MenuEvent event) {
		// System.out.println(event.getMenuItem().getCurrent().getText());
		List<List<String>> temp = Lists.partition(event.getMenuItem()
				.getPages(), 2);
		for (List<String> list : temp) {
			List<AnnotatedPage> pages = new ArrayList<AnnotatedPage>();
			for (String link : list) {
				try {
					pages.add(annotate(link));
				} catch (Exception e) {
				}
			}
			AnnotatedPageEvent pageEvent = new AnnotatedPageEvent(pages, event
					.getMenuItem().getCurrent(), event.getStoreId());
			eventBus.post(pageEvent);
			System.out.println("\tANNIE doned with " + list.size() + " pages");
		}
	}
}
